
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <assert.h>

#include "Boolean.h"
#include "Number.h"
#include "Object.h"
#include "Reference.h"
#include "Scope.h"
#include "String.h"
#include "UnaryExpression.h"
#include "Undefined.h"
#include "Value.h"
#include "ValueType.h"

UnaryExpression::UnaryExpression(unary_operation_t op, Expression* exp)
    : m_op(op), m_exp(exp)
{}

Value* UnaryExpression::evaluate(Scope* scope)
{
    return (this->*m_op)(scope);
}

Value* UnaryExpression::bitwise_not(Scope* scope) const
{
    // 1. Evaluate expression
    // 2. Call Get Value on the result
    // 3. Call toInt32
    Number* exp = m_exp->evaluate(scope)->getValue()->toInt32();

    // 4. Apply bitwise complement to the result
    // 5. Return the result
    return Number::bitwise_not(exp);
}

Value* UnaryExpression::delete_op(Scope* scope) const
{
    // Ecma 11.4.1
    // 1. evaluate expression
    Value* exp = m_exp->evaluate(scope);
    CHECK_EXCEPTION(exp);

    // 2. if type is not reference, return true
    if (exp->getType() != VT_REFERENCE)
    {
        return Boolean::True;
    }

    // 3. Call getBase
    Value* base = exp->reference()->getBase();
    CHECK_EXCEPTION(base);

    // 4. Call getpropertyname
    String* name = exp->reference()->getPropertyName();
    CHECK_EXCEPTION(name);

    // 5. Call the [[delete]] method on base, deleting name.
    // 6. Return that result
    return base->object()->deleteProperty(name);
}

Value* UnaryExpression::identifier(Scope* scope) const
{
    return scope->lookup(m_exp->evaluate(scope)->string());
}

Value* UnaryExpression::logical_not(Scope* scope) const
{
    // Ecma 11.4.9
    // 1. Evaluate UnaryExpression
    // 2. Call GetValue on that
    Value* exp = m_exp->evaluate(scope)->getValue();
    CHECK_EXCEPTION(exp);

    // 3. Call toBoolean on that
    Boolean* bexp = exp->toBoolean();
    CHECK_EXCEPTION(bexp);

    // 4. If that's true, return false.
    // 5. return true
    return bexp->isTrue() ? Boolean::False : Boolean::True;
}

Value* UnaryExpression::minus(Scope* scope) const
{
    // Ecma 11.4.7
    // 1. Evaluate exp
    // 2. Call getValue on that
    Value* exp = m_exp->evaluate(scope)->getValue();
    CHECK_EXCEPTION(exp);

    // 3. Call toNumber on that
    Number* nexp = exp->toNumber();

    // 4,5,6 return the negation of that. (See operator- for Number)
    return Number::minus(nexp);
}

Value* UnaryExpression::plus(Scope* scope) const
{
    // Ecma 11.4.6
    // 1. Evaluate exp
    // 2. Call getValue on the result
    Value* exp = m_exp->evaluate(scope)->getValue();

    // 3. Call toNumber on that
    // 4. Return it
    return exp->toNumber();
}

Value* UnaryExpression::post_decrement(Scope* scope) const
{
    // Ecma 11.3.2
    // 1. Evaluate expression
    // 2. Call getValue on the result
    Value* exp = m_exp->evaluate(scope);
    Value* exp_val= exp->getValue();

    // 3. Call toNumber
    Number* num = exp_val->toNumber();

    // 4. Subtract the value 1 from num using the - operator.
    // 5. Call putvalue (exp, that)
    Number* one = new Number(1);
    exp->putValue(Number::subtract(num, one));

    // 6. Return num.
    return num;
}

Value* UnaryExpression::post_increment(Scope* scope) const
{
    // Ecma 11.3.1
    // 1. Evaluate expression
    // 2. Call getValue on the result
    Value* exp = m_exp->evaluate(scope);
    Value* exp_val= exp->getValue();

    // 3. Call toNumber
    Number* num = exp_val->toNumber();

    // 4. Add the value 1 to num using the + operator.
    // 5. Call putvalue (exp, that)
    Number* one = new Number(1);
    exp->putValue(Number::add(num, one));

    // 6. Return num.
    return num;
}

Value* UnaryExpression::pre_decrement(Scope* scope) const
{
    // Ecma 11.4.5
    // 1. Evaluate Expression
    Value* rexp = m_exp->evaluate(scope);

    // 2. Call getValue on that result
    // 3. Call toNumber on that
    Number* vexp = rexp->getValue()->toNumber();

    // 4. Subtract the value 1 to that, using the same rules as for -
    // 5. Call putValue on rexp with the result
    Number* one = new Number(1);
    Number* res = Number::subtract(vexp, one);
    rexp->putValue(res);

    // 6. Return the result
    return res;
}

Value* UnaryExpression::pre_increment(Scope* scope) const
{
    // Ecma 11.4.4
    // 1. Evaluate Expression
    Value* rexp = m_exp->evaluate(scope);

    // 2. Call getValue on that result
    // 3. Call toNumber on that
    Number* vexp = rexp->getValue()->toNumber();

    // 4. Add the value 1 to that, using the same rules as for +
    // 5. Call putValue on rexp with the result
    Number* one = new Number(1);
    Number* res = Number::add(vexp, one);
    rexp->putValue(res);

    // 6. Return the result
    return res;
}

Value* UnaryExpression::typeof_op(Scope* scope) const
{
    // Ecma 11.4.3
    // 1. Evaluate expression
    Value* exp = m_exp->evaluate(scope);

    // 2. If type of exp is not Reference, go to step 4.
    if (exp->getType() == VT_REFERENCE)
    {
        // 3. If GetBase of exp is null, return "undefined".
        if (exp->reference()->getBase()->getType() == VT_NULL)
        {
            return new String("undefined");
        }
    }

    // 4. Call get value on result
    // 5. Return a string determined by the type according to the table.
    exp = exp->getValue();
    switch (exp->getType())
    {
        case VT_UNDEFINED:
            return new String("undefined");

        case VT_NULL:
            return new String("object");

        case VT_BOOLEAN:
            return new String("boolean");

        case VT_NUMBER:
            return new String("number");

        case VT_STRING:
            return new String("string");

        case VT_OBJECT:
            if (exp->object()->isCallable())
            {
                return new String("function");
            }
            return new String("object");

        default:
            // We shouldn't get here.
            assert(false);
    }
    return Undefined::undefined;
}

Value* UnaryExpression::void_op(Scope* scope) const
{
    // Ecma 11.4.2
    // 1. Evaluate exp
    // 2. Call getValue on it
    m_exp->evaluate(scope)->getValue();

    // 3. Return undefined
    return Undefined::undefined;
}

